Branches conditionnelles et boucles
Table des matières
1. Branches conditionnelles et assertions
1.1. if ... else ...
L'extrait ci-dessous implémente une fonction maximum
.
def maximum(a,b): if a >= b: # Si a≥ b return a # renvoyer a else: # sinon return b # renvoyer b
\hspace{0.4cm}
Règles de syntaxe pour l'utilisation if
:
- Le
if
est suivi une expression (a>=b
) qui s'évalue en un booléen, suivi de «:
». - Les instructions qui sont à exécuter sous cette condition sont indentées.
- Le
else:
, optionnel, est au même niveau d'indentation que leif
. - Les instructions à exécuter sous le
else
sont indentées.
Le if
signifie «si» et le else
«sinon». On peut utiliser une branche if
sans la compléter d'une branche else
.
Une fonction max
est déjà prédéfinie en Python
et supporte un nombre arbitraire d'arguments.
max(-4,5,2)
renvoie $5$.
Écrire une fonction valeur_absolue
, qui renvoie la valeur absolue de son argument.
Python
prédéfinit une fonction abs
qui calcule la valeur absolue.
1.2. Opérateurs de comparaison et opérations sur les booléens
Opérateur | Signification | Exemple |
---|---|---|
> , < |
supérieur/inférieur stricte | 3 ** 2 > 2 ** 3 |
>= , <= |
supérieur/inférieur ou égal | x * x >= 0 |
== |
égal | 7 // 2 == 3 |
!= |
différent | 7 % 2 != 0 |
Ces opérateurs renvoient un booléen, c'est-à-dire True
ou False
. Pour chaque exemple plus haut, préciser à droite s'il renvoie True
ou False
.
Écrire une fonction est_impair
qui prend un entier en argument et qui renvoie True
s'il est impair, et False
sinon.
Opérateur | Signification | Exemple |
---|---|---|
not |
négation | (not a>b ) $\ssi$ a<=b |
and \hspace{0.2cm} ( & ) |
conjonction (et) | (a>=b and a<=b ) $\ssi$ a==b |
or \hspace{0.4cm} ( $\mathbb{\vert}$ ) |
disjonction (ou) | (a>b or a==b ) $\ssi$ a>=b |
In [1]: (7**8 > 8**7) or (7**8 < 8**7) Out[1]: True # Rmq : les parenthèses sont inutiles
In [2]: (7**7) % 2 == 1 and not (7**7) % 3 == 0 Out[2]: True # Idem
Écrire une fonction est_grand_impair
de deux lignes qui prend un entier en argument et qui renvoie True
s'il est impair et supérieur à $100$, et False
sinon.
Évaluation paresseuse
En Python
, l'évaluation de conditions booléennes contenant des opérateurs est paresseuse. Par exemple, pour évaluer cond1 and cond2
, l'interpréteur va commencer par évaluer cond1
, et si cond1
est False
, il n'évaluera pas cond2
. C'est utile lorsque l'évaluation de cond2
donnerait une erreur si cond1
n'était pas vérifiée.
# La fonction divise renvoie True si a divise b # On suppose que b∞nℕ* et a∞nℕ def divise(a,b): # L'instruction b % a donne une erreur si a == 0 # mais ici, elle n'est exécutée que si a != 0 return a != 0 and b % a == 0
l = [7, 1, 0, 3, 0] i, c = 0, 0 # Ici, l[i] renverrait une erreur # lorsque i = len(l) # On cherche le nb d'éléments de l avant le premier élément nul while i < len(l) and l[i] != 0: c += 1 i += 1 print(c)
Sachant que $0$ divise $0$, écrire une nouvelle fonction divise
de deux lignes qui soit correcte pour tout $b\in\N$.
1.3. Schémas de retours
Dans une fonction, l'instruction return
interrompt l'exécution du corps de la fonction et peut simplifier la logique.
Les deux schémas suivants sont équivalents.
def fn(…): if C: return A else: return B
def fn(…): if C: return A return B
Le schéma de gauche suivant est à éviter, étant équivalent à celui de droite.
def fn(…): if C: return True else: return False
def fn(…): return C
Qu'affichent les extraits suivants, et que renvoient-ils ?
def f(a): if a % 2 > 0: print("impair") return a print("pair") f(5)
def g(a): if a % 2 > 0: return(a) print("impair") print("pair") g(5)
def h(a): if a % 2 > 0: print("impair") print("pair") h(5)
\vspace*{2\baselineskip}
1.4. Branches imbriquées et disjonctions de cas
La fonction positifs_non_nuls
prend deux arguments et renvoie True
si ses deux arguments sont positifs, et non tous les deux nuls. Les trois extraits suivants sont équivalents.
def positifs_non_nuls(a,b): if a > 0: if b >= 0: return True else: return False if a >= 0: if b > 0: return True return False
def positifs_non_nuls(a,b): if a > 0: return b >= 0 if a == 0: return b > 0 return False
def positifs_non_nuls(a,b): return (a >= 0 and b > 0) or (a > 0 and b >= 0)
Écrire une fonction fizzbuzz
qui prend un argument un entier n
et qui imprime «Fizz» si n
est divisible par $3$, «Buzz» si n
est divisible par $5$, «FizzBuzz» si n
est à la fois divisible par $3$ et $5$, et simplement l'entier n
s'il n'est divisible ni par $3$, ni par $5$.
La fonction print
passe à la ligne, donc les instructions successives print("Fizz"); print("Buzz")
n'affiche pas «FizzBuzz».
Branche elif
Plutôt qu'imbriquer des branches les unes dans les autres, on peut utiliser la syntaxe elif
pour «Sinon si».
Les deux extraits suivants sont équivalents.
if note > 16: print("Félicitations") else: if note > 13: print("Bien") else: if note > 10: print("Admis") else: print("Non admis")
if note > 16: print("Félicitations") elif note > 13: print("Bien") elif note > 10: print("Admis") else: print("Non admis")
Écrire une fonction nb_grands
qui prend en argument trois nombres a,b,c
et qui renvoie le nombre de ses arguments qui sont $\geq 100$. Par exemple, l'appel nb_grands(11, 120, 25)
renverra $1$.
Indication : On pourra éventuellement utiliser une fonction intermédiaire ne prenant que deux arguments, ou 1.
1.5. Assertions et jeux de tests
Les assertions sont des instructions particulières qui prennent la forme assert(condition)
. Si condition
s'évalue en True
, cette instruction ne fait rien, et si condition
est False
, l'instruction interrompt l'exécution du programme et signale une erreur.
Dans le shell, essayer les commandes assert(1+1==2)
et assert(1+1==3)
.
On utilisera les assertions
- pour tester les valeurs renvoyées par une fonction sur des exemples,
- éventuellement pour signaler des conditions qui doivent être vérifiées par les arguments d'une fonction.
La fonction suivante prend en argument trois entiers a,b,c
, avec $a\neq 0$, et renvoie le nombre de solutions réelles de l'équation $ax^2 + bx + c = 0$ en calculant le discriminant du polynôme.
### Nombre de solutions d'un trinôme def nb_sols(a,b,c): assert(a != 0) # si a=0, ce n'est pas un trinôme d = b*b - 4*a*c # Δ = b² − 4ac if d > 0: return 2 # early return : le reste n'est pas évalué if d == 0: return 1 return 0 # dans ce cas, c'est que Δ < 0 # Les assertions suivantes sont en dehors du corps de la fonction # Elles testent le comportement de la fonction assert(nb_sols(1,0,1) == 0) # x² + 1 = 0 n'a pas de solutions assert(nb_sols(1,2,1) == 1) # x² + 2x + 1 = 0 a une unique solution : −1 assert(nb_sols(1,0,-1) == 2) # x² − 1 = 0 a deux solutions : ± 1
La première ligne commençant par ###
délimite une cellule, concept propre à certains IDE. Le raccourci Ctrl+Entrée
permet d'évaluer tout le code de la cellule actuelle. En l'occurrence, cela évalue la définition de la fonction et les trois assertions (tests) qui suivent.
- Recopier le code précédent sans les commentaires, mais avec les assertions et l'évaluer.
- Dans le shell, essayer la commande
nb_sols(0,1,1)
. - Modifier légèrement le code de la fonction pour qu'elle ne soit plus correcte. Réévaluer le code et les tests.
- Réécrire (modifier dans l'éditeur) le corps de la fonction
nb_sols
en utilisant une première conditiond >= 0
et une deuxième instructionif
imbriquée. Évaluer à nouveau, pour vérifier que les tests passent. - ★ Modifier la fonction
nb_sols
pour qu'elle ne soit plus correcte, mais que les tests passent quand même.
Un bon jeu de tests doit couvrir les différents cas généraux possibles, et d'éventuels cas critiques (arguments nuls, ou liste vide par exemple).
Le développement piloté par les tests (test-driven development), est une pratique de développement qui consiste à concevoir un programme (une fonction, un logiciel) en écrivant les tests avant d'écrire le code source. En plus d'assurer la correction de la fonction finale (si les tests passent), cette pratique force à s'interroger sur le comportement que devra avoir la fonction dans les cas critiques.
On veut réécrire la fonction nb_sols
pour qu'elle fonctionne également si $a=0$. On suppose à la place que les trois entiers a,b,c
sont non tous nuls, et elle doit renvoyer le nombre de solutions réelles de l'équation $ax^2 + bx + c = 0$.
- Écrire deux tests (en plus des trois précédents) qui couvrent les nouveaux cas.
- Écrire la nouvelle fonction
nb_sols
. On utilisera une assertion pour s'assurer les arguments vérifient la condition donnée.
1.6. Exercices
On veut écrire une fonction mediane3
qui prend trois entiers a,b,c
en argument et qui renvoie la médiane de ces trois nombres.
- Proposer un jeu de deux ou trois tests.
- Écrire la fonction
mediane3
.
Les années bissextiles sont celles divisibles par 400, et aussi celles divisibles par 4 mais non divisible par 100. On veut écrire une fonction bissextile
qui prend un entier en argument et renvoie True
si l'année est bissextile, et False
sinon.
- Proposer un jeu de deux ou trois tests.
- Écrire la fonction
bissextile
. - ★ Quel le jour de la semaine était-on le 1ᵉʳ janvier de l'an 1000 ?
Écrire une fonction type_triangle
qui prend en argument trois entiers strictement positifs et qui affiche le type de triangle dont ces entiers sont les longueurs des côtés.
Les types possibles sont «plat», «isocèle» (et non plat), «équilatéral», «scalène» (aucun des trois premiers), ou «impossible».
assert(type_triangle(1,1,2) == "plat") assert(type_triangle(4,1,2) == "impossible") assert(type_triangle(4,3,2) == "scalène") assert(type_triangle(4,3,3) == "isocèle") assert(type_triangle(3,3,3) == "équilatéral") def type_triangle(a,b,c): if a == b+c or b == a+c or a == b+c: return "plat" if a > b+c or b > a+c or c > a+b: return "impossible" if a == b and b == c: return "équilatéral" if a == b or b == c or a == c: return "isocèle" return "scalène"
2. Boucles
2.1. Boucles for
Les boucles permettent de répéter un bloc d'instructions. La boucle for
de Python
permet de répéter des instructions un nombre prédéterminé $n$ de fois. On l'utilise avec l'instruction range(n)
qui énumère les entiers de $\boxed{0 \text{ à }n-1}$ (il y en a $n$).
Dans les extraits suivants ( essayer), la variable i
va prendre successivement les valeurs de l'énumération, et pour chaque valeur, les instructions à l'intérieur de la boucle seront exécutées.
for i in range(5): print("Coucou")
for i in range(5): print(i)
La fonction range
peut également être appelée avec 2 ou 3 arguments :
- L'instruction
range(a,b)
énumère les entiers de $a$ (inclus) à $b$ (exclus), donc les entiers de $\db{a,b-1}$. $\xcancel{\heartsuit}$ L'instruction
range(a,b,c)
énumère les entiers de $a$ (inclus) à $b$ (exclus), par pas de $c$. C'est-à-dire les entiers $a,a+c, a+2c,\dots$.Le pas peut être négatif, auquel cas il faut choisir $b\lt a$.
La fonction suivante calcule la somme $S = \sum_{i=1}^n i$ des entiers de $1$ à $n$.
def somme(n): s = 0 # La somme sera stockée dans la variable s for i in range(1,n+1): # i prend les valeurs entières de 1 à n s = s + i # On incrémente la variable s return s # Noter le retour à l'indentation précédente
Noter les « :
» obligatoires dans la ligne du for
, et l'indentation qui suit. Ce sont les instructions sous l'indentation qui seront répétées.
Que vaut la variable s
après l'exécution des extraits suivants ?
s = 0 for i in range(2,5): s = s + 1 s = s + 1
s = 0 for i in range(2,5): s = s + 1 s = s + 1
L'incrémentation s = s + i
peut également s'écrire s += i
. Les opérateurs *=
, /=
et -=
existent également.
Écrire une fonction factorielle
, prenant un entier n
en argument et renvoyant $n! = 1 \times 2 \times \dots \times n$.
Le module math
contient une fonction math.factorial
pour calculer la factorielle d'un entier.
Écrire une fonction u
, prenant un entier n
en argument et renvoyant le terme $u_n$ de la suite définie par $u_0=1$ et la relation de récurrence $\forall n\geq 0,\, u_{n+1} = 2 u_n - 5$.
On veut écrire une fonction somme_impairs
qui prend en argument deux entiers relatifs $a,b$ et qui renvoie la somme des entiers impairs compris entre $a$ et $b$ (inclus).
- Écrire un jeu de tests sur la fonction
somme_impairs
. - Implémenter
somme_impairs
. On écrira deux versions, l'une en utilisant le troisième argument derange
, l'autre sans.
Si $(u_n)_{n\in\N^*}$ est une suite, on note $\sum_{k=1}^n u_k$ la somme des termes d'indice $k$ de la suite, pour $k$ variant de $1$ à $n$. Autrement dit, $\displaystyle\sum_{k=1}^n u_k = u_1 + u_2 + \dots + u_n$, et par convention $\displaystyle\sum_{k=1}^0 u_k = 0$.
\enlargethispage{1.5cm}
On considère une suite $(u_n)$ définie par $u_0 = 1$ et $\forall n\in\N,\,u_{n+1} = 2u_n + 1$. On note, pour $n\geq 0$, $$S_n = \sum\limits_{k=1}^{n} u_k = u_1 + u_{2} + \dots + u_{n} \et T_n = \sum\limits_{k=n}^{2n} u_k = u_n + u_{n+1} + \dots + u_{2n}.$$
- Écrire une fonction
S
d'argument $n\in\N$ qui renvoie $S_n$. - Écrire une fonction
T
d'argument $n\in\N$ qui renvoie $T_n$.
2.2. Boucles while
Une boucle while
(tant que) répète une série d'instruction tant qu'une condition est vérifiée.
- Comprendre, puis exécuter () le premier extrait.
Quel résultat est-ce que l'exécution du deuxième extrait va produire ? Vérifier.
Utiliser un des boutons en haut du shell pour interrompre l'exécution (l'éclair jaune).
i = 0 while i < 3: # tant que i< 3 print(i) i = i+1 # ou i += 1
i = 0 while i < 5: # tant que i< 5 print(i) i = i+1 # ou i += 1
Le premier extrait est équivalent une boucle for i in range(3)
. D'une manière générale, toute boucle for
peut être transformée en une boucle while
, mais l'inverse n'est pas vrai.
Écrire des extraits de code affichant (avec print
)
le plus petit entier $n$ vérifiant $n! > 10^n$.
★ Justifier a priori son existence.
le plus grand entier $n$ tel que $2^n \leq 10^7$.
En donner une expression explicite.
2.3. Exemples
Quelques extraits
Pour chaque extrait suivant, indiquer la valeur de la variable a
à la fin de l'exécution.
# Extrait 1 a = 0 for i in range(n): a += 1
# Extrait 2 a = 0 for i in range(1,n+1): a += 1
# Extrait 3 a = 1 for i in range(n): a = 2*a
# Extrait 4 a = 1 while a <= 10: a *= 2
# Extrait 5 a = 1 while 2*a <= 10: a = 2*a
# Extrait 6 a = 1 while a*a <= 100: a = a*a
# Extrait 7 a = 40 while a % 2 == 0: a = a // 2
# Extrait 8 a = 0 for i in range(100): a += (-1) ** i
Récurrence double
On considère une suite $(u_n)_{n\in\N}$ vérifiant une relation de récurrence double comme $\forall n\in\N,\,u_{n+2} = 2u_{n+1} + u_n$.
Pour calculer la valeur de $u_n$, on maintient deux variables $a, b$ qui contiennent deux valeurs successives de la suite, que l'on met à jour avec des affectations par couple, dont le fonctionnement est rappelé dans l'extrait suivant.
In [1]: a, b = 3, 5 # Initialisation de deux variables : a=3=u₀ et b=5 = u₁ In [2]: a, b = b, 2*b + a # a = 5 et b = 2× 5+3 In [3]: a, b Out[3]: (5, 13) # a = u₁ et b = u₂
Écrire une fonction fib
qui prend en argument un entier $n\in\N^*$ et renvoie le terme $F_n$ de la suite de Fibonacci définie par $F_1 = F_2 = 1$ et $\forall n\in\N^*,\, F_{n+2} = F_{n+1} + F_n$.
On considère deux suites $x_n$ et $y_n$ définies par $\forall n\in\N,\,\begin{cases} x_{n+1} = x_n - 2 y_n \\
y_{n+1} = x_n + y_n \end{cases}$. Écrire une fonction xy
qui prend un entier $n$, et les valeurs x0
, y0
en argument et renvoie le couple $(x_n, y_n)$.
2.4. Boucles imbriquées
Dans l'extrait de gauche qui suit, la même boucle for i
est exécutée deux fois. L'extrait de droite est donc équivalent.
s = 0 for i in range(3): s = s + i for i in range(3): s = s + i
s = 0 for j in range(2): for i in range(3): s = s + i
Que vaut la variable s
après l'évaluation d'un des deux extraits précédents ?
- Que valent les variables
s1,s2,s3
après l'exécution des extraits suivants ? - Dans chaque cas, combien de fois l'instruction d'incrémentation
s = s + ...
est-elle exécutée ?
s1 = 0 for i in range(3): # i prend les valeurs 0,1,2 for j in range(3): # j prend les valeurs 0,1,2 s1 = s1 + j
s2 = 0 for i in range(3): for j in range(3): s2 = s2 + i + j
s3 = 0 for i in range(3): for j in range(i,3): s3 = s3 + i + j
Une itération avec deux boucles for
imbriquée sur deux ensembles $I$, $J$ indépendant revient à itérer sur les couples du produit cartésien $I\times J$. En particulier, les instructions à l'intérieur sont exécutées $|I|\times |J|$ fois.
for i in range(n): for j in range(m): # Exécuté nm fois f(i, j)
for i in range(n): for j in range(n): # Exécuté n² fois f(i, j)
for i in range(n): for j in range(i, n): # Exécuté \frac{n(n+1)}{2} = O(n²) fois f(i, j)
- Écrire deux fonctions prenant en argument un entier $n$ et renvoyant les valeurs des sommes $$S_1 = \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n} \frac{1}{ij} = \sum_{1\leq i,j\leq n} \frac{1}{ij} \quad \et \quad S_2 = \sum\limits_{i=1}^{n} \sum\limits_{j=i+1}^{n} \frac{1}{ij} = \sum_{1\leq i\lt j\leq n} \frac{1}{ij}.$$
- Π Comment calculer chaque somme en utilisant au plus $O(n)$ opérations, c'est-à-dire un nombre d'opérations $\leq Kn$, pour une constante $K$ indépendante de $n$ ?
- On a $\sum_{i,j} a_{i}a_j = (\sum_i a_i)^2$
def S1(n): s = 0 for i in range(1,n+1): for j in range(1,n+1): s += 1/(ij) return s def S2(n): s = 0 for i in range(1,n+1): for j in range(j+1,n+1): s += 1/(ij) return s
- En utilisant deux boucles imbriquées, écrire une fonction
somme_deux_carres
qui prend en argument un entier $n$ et détermine si $n$ est la somme de deux carrés, c'est-à-dire s'il existe deux entiers $i,j$ tels que $n = i^2 + j^2$. Pour déterminer si un entier
n
est le carré d'un autre entier, on peut utiliser l'expressionmath.floor(math.sqrt(n))**2 == n
En utilisant celle-ci, réécrire la fonction précédente en utilisant une seule boucle.
Grâce aux fonctions sur les flottants, cette nouvelle procédure est beaucoup plus rapide, mais elle n'est pas correcte pour des entiers très grands, car les opérations sur les flottants ne sont pas exactes.
- Imprimer les nombres premiers $\leq 100$ qui sont sommes de deux carrés, puis ceux qui ne le sont pas. Que conjecturer ?
Les nombres triangulaires sont les nombres de la forme $t_i = \frac{i(i+1)}{2}$, pour $i\in\N^*$. Les carrés parfaits sont les nombres de la forme $j^2$, pour $j\in\N$.
- En utilisant deux boucles imbriquées, écrire du code
Python
qui affiche tous les entiers $n\in\N^*$ inférieurs à 1000 qui sont à la fois des nombres triangulaires et des carrés parfaits. ★ Vérifier que $8t_n +1$ est un carré. En déduire qu'il existe une infinité d'entiers de $\N^*$ qui sont à la fois des nombres triangulaires et des carrés parfaits.
Indication : Si $t_n$ est un carré parfait, en construire un autre.
★ Montrer que $1$ est le seul nombre triangulaire qui soit un cube parfait.
On admettra la conjecture de Catalan, démontrée par Mihăilescu, selon laquelle l'équation $a^b - c^d = 1$ n'admet aucune solution entière non triviale autre que $3^2 - 2^3 = 1$.
# Affichage des nbs triangulaires qui sont des carrés import math for i in range(math.floor(math.sqrt(1000)) + 1): n = i ** 2 j = 0 while (j * (j+1)) // 2 < n: j += 1 if (j * (j+1)) // 2 == n: print n
3. Éléments d'analyse des algorithmes : un exemple
La fonction ci-contre prend en argument deux entiers positifs $n,b$, avec $b\gt 0$. Que renvoie-t-elle ?
def mystere(n, b): m = n while m >= b: m = m - b return m
3.1. Terminaison de l'algorithme
Quand un algorithme utilise une boucle while
, il faut vérifier que celle-ci termine, c'est-à-dire que la condition d'arrêt finit (ici m < b
) par être vérifiée.
Pour cela, on utilise un variant de boucle. Dans la majorité des cas, c'est une variable prenant des valeurs entières positives dont la valeur décroît strictement à chaque itération de la boucle. Comme il n'existe pas de suite d'entiers naturels strictement décroissante, cela justifie que la boucle ne tourne qu'un nombre fini de fois, donc que la condition d'arrêt est atteinte.
Dans la fonction mystere
, la variable m
prend des valeurs entières positives (car la boucle s'arrête si $m\lt 0$) et strictement décroissantes (car $b\gt 0$). Cela justifie la terminaison de l'algorithme. On a également identifié l'hypothèse importante ($b\gt 0$).
3.2. Correction de l'algorithme
La fonction mystere
détermine le reste de la division euclidienne de n
par b
.
Pour justifier qu'elle fait bien cela, on utilise un invariant de boucle. On identifie une quantité qui ne change pas de valeur, ou une assertion mathématique qui reste vraie à chaque itération de la boucle.
3.2.1. Un premier invariant de boucle
Durant l'exécution de la boucle de la fonction mystere
, on a toujours
$$m \equiv n [b].$$
Formellement, cela se démontre par récurrence.
- C'est vrai au début de la boucle, car $m$ a pour valeur initiale $n$.
- Si c'est vrai à la fin d'une itération de la boucle, en notant $m^+$ la nouvelle valeur de $m$ dans l'itération d'après, on a $m^+ = m - b$, donc $m^+ \equiv m- b \equiv m \equiv n [b]$, d'après l'hypothèse de récurrence.
3.2.2. Un second invariant
On peut énoncer un invariant plus précis, sous la forme d'une propriété qui dépend du nombre d'itération de la boucle : $$\text{Après la i-ème itération de la boucle, } m = n - ib$$
Cet invariant se justifie également par récurrence.
3.2.3. Preuve de la correction
On peut maintenant justifier la correction de l'algorithme.
- Une fois la boucle terminée, d'après la condition d'arrêt, on a $m\lt b$.
- Si on note $m^{-}$ la valeur précédente de la variable
m
, on a $m = m^{-} - b$, et comme $m^{-}\geq b$, on a $m\geq 0$. - D'après le premier invariant de boucle, on a $m\equiv n [b]$
Ces trois points impliquent que la valeur finale de $m$, retournée par la fonction, est bien le reste de la division euclidienne de $n$ par $b$.
3.3. Complexité de l'algorithme
On appelle complexité d'un algorithme le nombre d'opérations élémentaires exécutées en fonction de la taille des paramètres d'entrée.
Comme le quotient de la division euclidienne de $n$ par $b$ est $\lfloor \frac{n}{b}\rfloor$, d'après le second invariant de boucle, la boucle de la fonction mystere
sera exécutée $\lfloor \frac{n}{b}\rfloor$ fois.
À l'intérieur de la boucle, on exécute une soustraction et une affectation. La fonction réalise donc au total $\lfloor \frac{n}{b}\rfloor$ soustractions et affectations et $\lfloor \frac{n}{b}\rfloor + 1$ comparaisons.
On dira que la complexité est en $O(\frac{n}{b})$ opérations élémentaires, c'est-à-dire que le nombre d'opérations élémentaires est majoré par $K \frac{n}{b}$, pour une certaine constante $K$.
4. Algorithme d'Euclide
On rappelle le principe de l'algorithme d'Euclide, permettant de calculer le pgcd de deux entiers $a\geq b$.
- On effectue la division euclidienne de $a$ par $b$, qui s'écrit $a = bq + r$, avec $r\in\db{0,b-1}$.
- On recommence avec les entiers $b$ et $r$ : on écrit la division euclidienne de $b$ par $r$.
- On s'arrête quand une des divisions euclidiennes donne $0$. Le pgcd de $a$ et $b$ est alors le dernier reste non nul trouvé.
Pour calculer le pgcd de $38$ et $16$ :
- $38 = 2\times 16 + 6$
- $16 = 2\times 6 + 4$
- $6 = 1 \times 4 + 2$
- $4 = 2\times 2 + 0$
Le pgcd est donc $2$.
Soient $a,b$ deux entiers et $a = bq + r$ la division euclidienne de $a$ par $b$. On note $\mc D(a,b)$ l'ensemble des diviseurs communs à $a$ et $b$. Alors $\mc D(a,b) = \mc D(b,r)$. En particulier, $\op{pgcd}(a,b) = \op{pgcd}(b,r)$.
- ♥ Écrire une fonction
pgcd_euclide
qui prend en argument deux entiers naturelsa,b
et renvoie leur pgcd. Justifier la terminaison de votre algorithme en explicitant un invariant de boucle.
En déduire la correction de l'algorithme.
- ★ On considère la suite de Fibonacci définie par $F_1 = 1$, $F_2 = 1$ et $\forall n\in\N^*, F_{n+2} = F_{n+1} + F_n$. Montrer par récurrence d'ordre $2$ que si $a\leq F_n$ et $b\leq F_n$ alors l'algorithme d'Euclide termine en au plus $n$ étapes.
- ★ En déduire que la complexité de l'algorithme d'Euclide est logarithmique en $\min(a,b)$.
Algorithme d'Euclide étendu
Dans le calcul du pgcd de $a,b$ par l'algorithme d'Euclide, notons $a_n, b_n$ les entiers considérés à l'issue de la $n$-ième itération de la boucle, définis par $$\begin{cases}a_0 = a \\ b_0 = b \end{cases}\quad \et \quad \forall n\in\N,\,\begin{cases}a_{n+1} = b_n \\ b_{n+1} = r_n \end{cases}, \text{ où } a_n = b_n q_n + r_n \text{ est la division euclidienne.}$$
On se convainc aisément que pour tout $n\in\N$, $a_n$ et $b_n$ sont des combinaisons linéaires entières des entiers $a,b$ d'origine, c'est-à-dire qu'il existe $u_n,v_n\in\Z$ tels que $b_n = u_n a + v_n b$. En particulier, à la dernière étape, $\op{pgcd}(a,b)$ est lui-même une combinaison linéaire de $a,b$ :
Soient $a,b\in\N$. Il existe un couple $(u,v)\in\Z^2$ tel que $$\op{pgcd}(a,b) = au + bv.$$
L'ensemble $\{au + bv\,;\, (u,v)\in\Z^2\}$ est l'ensemble des multiples de $\op{pgcd}(a,b)$.
Écrire une fonction euclide_etendu
qui prend en argument deux entiers $a,b \geq 1$ et renvoie un couple de Bézout de $a,b$, c'est-à-dire un couple d'entiers relatifs $(u,v)$ tel que $au + bv = \op{pgcd}(a,b)$.
def eucl_etendu(a0,b0): # Inutile d'échanger a et b si b> a, l'algorithme fonctionne # quand même, avec une étape qui ne fait justement qu'échanger a # et b. a, b = a0, b0 # On utilise d'autres variables, pour clarifier ua, va = 1, 0 # a = uₐ a₀ + vₐ b₀, invariant qui sera maintenu par la suite ub, vb = 0, 1 # b = u_b a₀ + v_b b₀, invariant while b != 0: r, q = a % b, a // b # On a r = a − bq = (uₐ a₀ + vₐ b₀) − q (u_b a₀ + v_b b₀) # Donc r = a₀ (uₐ − q u_b) + b₀ (vₐ − q v_b) a, b = b, r ur, vr = ua - q * ub, va - q * vb ua, va = ub, vb ub, vb = ur, vr # À l'issue de la boucle, le a est égal au pgcd return (ua, va)
5. Exercices
Déterminer la somme des $1000$ premiers entiers impairs.
On considère la suite définie par $u_n = \frac{1}{n^2+5}$. Écrire un programme qui affiche tous les termes de la suite $(u_n)$ qui sont strictement supérieurs à $10^{-3}$. Donner de tête un ordre de grandeur de leur nombre.
On considère la suite définie par $u_0=1$ et la relation de récurrence $\forall n\geq 0,\, u_{n+1} = 3 u_n - 3$. Déterminer le premier indice $n$ pour lequel $|u_n|\geq 10^6$.2
Soit $(u_n)$ la suite définie par $u_0 = 1$ et $\forall n\in\N,\,u_{n+1} = 2u_n + 2$. Écrire une fonction nb_un
qui prend en argument deux entiers $a,b$ et qui renvoie le nombre de termes de la suite qui vérifient $u_n \in [a,b]$.
On considère la suite définie par $u_0=0$ et $\forall n\geq 0,\,u_{n+1} = |u_n - n|$. Écrire une fonction prenant un entier n
en argument et qui renvoie $\max\limits_{0\leq k\leq n} u_k$ : la valeur maximale prise par la suite $(u_n)$ lors des $n+1$ premiers indices. On utilisera la fonction abs
pour calculer une valeur absolue.
On note $(F_n)$ la suite de Fibonacci. Écrire une fonction plus_petit_fib_sup
qui prend en entier $m$ et renvoie le premier indice $n$ tel que $F_n\geq m$. On proposera une solution avec une complexité linéaire en $n$, c'est-à-dire avec un nombre d'opérations majoré par $Cn$, pour une constante $C$.
- Écrire une fonction
plus_petit_diviseur_strict
qui prend en argument un entier $n\geq 2$ et qui renvoie le plus petit diviseur den
différent de $1$. - Écrire une fonction
plus_grand_diviseur_strict
qui prend en argument un entier $n\geq 2$ et qui renvoie le plus grand diviseur den
différent de lui-même.
Un entier parfait est un entier égal à la somme de ses diviseurs stricts. Écrire une fonction parfait
qui détermine si un entier $n\geq 2$ est parfait, en renvoyant un booléen.
Écrire une fonction somme_chiffres
qui calcule la somme des chiffres d'un entier en argument.
On n'utilisera pas la fonction str
.
Soit $n\in\N^*$. On dit que $b$ est un inverse de $a$ modulo $n$ si $ab\equiv 1[n]$.
- Écrire une fonction naïve
inv_naive
qui prend en argumenta
etn
et renvoie un inverse de $a$ modulo $n$. - En utilisant l'algorithme Euclide étendu, écrire une version
inv_mod
rapide.
On considère la fonction de Syracuse $$S\colon \N^*\ra\N^* \quad n\mapsto \begin{cases} \frac{n}{2} \si n\text{ est pair} \\ 3n + 1 \sinon. \end{cases}.$$ Pour $u_0 \in\N^*$, la suite $(u_n)_{n\in\N}$ définie par $\forall n\in\N,\,u_{n+1} = f(u_n)$ et appelée une suite de Syracuse.
Implémenter la fonction
S
.Il est conjecturé que quelle que soit la valeur de $u_0\in\N^*$, il existe un entier $n_0\in\N$ tel que $u_{n_0} = 1$. S'il existe, on note $h(u_0)$ le plus petit tel entier. On suppose dans la suite que cette conjecture est correcte.
- Écrire une fonction
h
prenant un argument un entier naturel non nulu0
et qui renvoie $h(u_0)$. - Écrire une fonction
M
qui prend en argument l'entieru0
et qui renvoie la valeur maximale des termes de la suite $(u_n)$.